package components;

/**
 * An implementation of a Leaky IntegpreferredISI and fire neuron, equipped with
 * optional homeostatic plasticity. 
 * @author zach
 *
 */
public class LIFNeuron extends AbstractNeuron{
	
	/** The default constant for homeostatic plasticity. */
	private static final double DEFAULT_HP_CONST = Math.pow(10, 7);
	
	/** The membrane potential. */	
	private double memPotential;
	
	/** The resting potential. */
	private double restPotential;
	
	/** Threshold for spiking. */
	private double threshold;
	
	/** The membrane resistance. */
    private double memResistance;

    /** The refractory period, ignore current. */
    private double refractory;
    
    /** The value the membrane potential is reset to after a spike. */
    private double reset;
    
    /** The incoming background current. */
    private double bgCurrent;
    
    /** The preferred inter-spike interval of this neuron. */
    private double preferredISI;
    
    /** 
     * The constant controlling the preferredISI of membrane resistance change for
     * homeostatic plasticity.
     */
    private double hpConstant = DEFAULT_HP_CONST;
   
    /** Is homeostatic plasticity on for this neuron? */
    private boolean hmP;
    
//    private boolean reportMemR;
//    private String fName = "MemRes.dat";
    
	/**
	 * Specified constructor, each parameter specified.
	 * @param restingPotential the resting potential
	 * @param threshold the threshold
	 * @param memResistance the membrane resistance
	 * @param timeConstant the time constant for the simulation
	 * @param excitatory whether or not the neuron is excitatory
	 */
	public LIFNeuron(double restingPotential, double threshold,
			double memResistance, double timeConstant, double refractory,
			final boolean excitatory, int index) {
		
		super(timeConstant, excitatory, index);
		this.restPotential = restingPotential;
		this.memPotential = restingPotential;
		this.threshold = threshold;
		this.memResistance = memResistance;
		this.refractory = refractory;

	}
	
	/**
	 * A bare-bones constructor
	 * @param timeConstant
	 * @param excitatory
	 */
	public LIFNeuron(double timeConstant, boolean excitatory, int index) {
		super(timeConstant, excitatory, index);
	}

	/**
	 * A constructor with blank parameters to be randomized.
	 * @param timeConstant
	 * @param excitatory
	 */
	public LIFNeuron(boolean excitatory, int index) {
		super(excitatory, index);
	}
	
	/**
	 * Copy constructor.
	 * @param n
	 */
	public LIFNeuron(LIFNeuron n) {
		super(n.getTimeConstant(), n.isExcitatory(), n.getIndex());
		this.restPotential = n.getRestPotential();
		this.refractory = n.getRefractory();
		this.memResistance = n.getMemResistance();
		this.threshold = n.getThreshold();
		this.memPotential = n.getMemPotential();
		this.bgCurrent = n.getBgCurrent();
		this.reset = n.getReset();
		this.setTime(n.getTime());
		setLastSpike(n.getLastSpike());
		this.setReporter(n.isReporter());
		this.setHmP(n.isHmP());
		this.preferredISI = n.getPreferredISI();
		this.setStdpOn(n.isStdpOn());
		this.setStdp(n.getStdp());
		this.setRecorder(n.getRecorder());
		
	}
	
	/**
	 * Creates a deep copy of this neuron and all the properties intrinsic to
	 * it at its point in time except coordinate. Values excluded from being
	 * copied include fanIn, fanOut, the snyapse buffer, and the decay bins.
	 * Coordinates are not copied to prevent collisions on the grid.
	 * @return a deep copy of this neuron excluding synapse related variables.
	 */
	public LIFNeuron deepCopy(){
		return new LIFNeuron(this);
	}
	
	/**
	 * Updates the LIFNeuron using a Euler integration with a specified 
	 * time constant according to standard leaky integpreferredISI and fire rule.
	 * @param timeStep the time step for integration
	 */
	public boolean update(double timeStep){
		
		if(getTime() > preferredISI && hmP) {
			double ISI = getTime()-getLastSpike();
			memResistance = memResistance * Math.exp((ISI - preferredISI) /
					(Math.pow(10, 8)));
		}
		
		double totCurrent = 0;
		if (getFanIn() != null) {	
			for (int i = 0, n = getFanIn().size(); i < n; i++) {
				Synapse s = getFanIn().get(i);
				totCurrent += s.getPSR();
				s.update(timeStep);
			}
			
		}
		
		double memChange;
		//If this is within a refractory period...
		if(getTime() <= getLastSpike() + refractory) {
			totCurrent = 0; //Do not accept incoming current
			memChange = 0;
		} else {
			//Calculate membrane potential using Euler Integration.
			memChange = ((timeStep/getTimeConstant()) *
					(-(memPotential-restPotential) +
							(memResistance * (totCurrent + bgCurrent))));		
			memPotential = memPotential + memChange;
		}		

		//Test if spiked, return answer
		boolean spiked = false;
		if(memPotential >= threshold) {
			spike();
			spiked = true;
		} 
		setTime(getTime() + timeStep);
		return spiked;	
	}
	
	/**
	 * 
	 * @param timeStep ms
	 * @param stimulation nA
	 * @return
	 */
	public boolean partialUpdate(double timeStep){
		if(getTime() > preferredISI && hmP) {
			double ISI = getTime()-getLastSpike();
			if(ISI >= preferredISI && ISI < 1.5 * preferredISI) {
				memResistance = memResistance * Math.exp((ISI - preferredISI) /
						(5 * Math.pow(10, 7)));	
			}else if(ISI > preferredISI && ISI > 1.5 * preferredISI){
				memResistance = memResistance * Math.exp((ISI - preferredISI) /
						(1 * Math.pow(10, 7)));	
			}else if (ISI < preferredISI && ISI > 0.5 * preferredISI){
				memResistance = memResistance * Math.exp((ISI - preferredISI) /
						(5 * Math.pow(10, 7)));	
			}else {
				memResistance = memResistance * Math.exp((ISI - preferredISI) /
						(1 * Math.pow(10, 7)));	
			}
		}
		
		double totCurrent = 0;
		if (getFanIn() != null) {	
			for (int i = 0, n = getFanIn().size(); i < n; i++) {
				Synapse s = getFanIn().get(i);
				totCurrent += s.getPSR();
				s.update(timeStep);
			}
			
		}
		boolean resting = false;
		double memChange;
		//If this is within a refractory period...
		if(getTime() <= getLastSpike() + refractory) {
			totCurrent = 0; //Do not accept incoming current
			resting = true;
		} 
		//Calculate membrane potential using Euler Integration.
		memChange = ((timeStep/getTimeConstant()) *
				(-(memPotential-restPotential) +
						(memResistance * (totCurrent + bgCurrent))));		
		memPotential = memPotential + memChange;
				

		if(memPotential >= threshold && !resting) {
			return true;
		} else {
			setTime(time + timeStep);
			return false;
		}
		
	}
	
	/**
	 * Executes a spike. This function updates the spike time for each
	 * afferent synapse, and adds each synapse to their respective target's
	 * buffer.
	 */
	public void spike() {
		setLastSpike(getTime()); //Set now as last time we spiked
//		System.out.println("Spk: " + getIndex() + " " + getTime());
		if(isReporter()) {
			getRecorder().spikeEvent(getIndex());
		}
		//ItepreferredISI over efferent synapses...
		for (int i = 0, n = getFanOut().size(); i < n; i++) {
			getFanOut().get(i).charge(getTime());
		}		
		
		if(isStdpOn()) {
			getStdp().applySTDPRule(getFanOut(), getTime(), false);
			getStdp().applySTDPRule(getFanIn(), getTime(), true);
		}
		
		memPotential = reset; //Reset membrane potential.
	}
	
	/**
	 * 
	 * @param timeStep
	 */
	public void spike(double timeStep) {
		spike();
		setTime(time + timeStep);
	}
	
	
//	/**
//	 * Carries out homeostatic plasticity relative to the LIF membrane
//	 * potential update. In this case HP changes the membrane resistance
//	 * based on if the ISI since the last spike is different from the 
//	 * preferred ISI of this neuron. The magnitude of this difference also
//	 * has direct bearing on the preferredISI of this change.
//	 */
//	private void homeostaticPlasticity(){
//		if(getTime() > preferredISI && hmP) {
//			double ISI = getTime()-lastSpike;
//			double delta = ISI - preferredISI;
//			double R = Math.abs(delta - preferredISI) / preferredISI;
//			if(ISI != preferredISI) {
//				memResistance = memResistance * Math.exp((delta *
//						Math.pow(10, R)) / hpConstant);	
//			}
//		}
//	}
	
	/*/*******************************************************************
	 * 						GETTERS AND SETTERS							 *
	 *********************************************************************/
	
	public double getMemPotential() {
		return memPotential;
	}

	public void setMemPotential(double memPotential) {
		this.memPotential = memPotential;
	}

	public double getRestPotential() {
		return restPotential;
	}

	public void setRestPotential(double restPotential) {
		this.restPotential = restPotential;
	}

	public double getThreshold() {
		return threshold;
	}

	public void setThreshold(double threshold) {
		this.threshold = threshold;
	}

	public double getMemResistance() {
		return memResistance;
	}

	public void setMemResistance(double memResistance) {
		this.memResistance = memResistance;
	}

	public double getRefractory() {
		return refractory;
	}

	public void setRefractory(double refractory) {
		this.refractory = refractory;
	}

	public double getReset() {
		return reset;
	}

	public void setReset(double reset) {
		this.reset = reset;
	}

	public double getBgCurrent() {
		return bgCurrent;
	}

	public void setBgCurrent(double bgCurrent) {
		this.bgCurrent = bgCurrent;
	}

	public double getPreferredISI() {
		return preferredISI;
	}

	public void setPreferredISI(double preferredISI) {
		this.preferredISI = preferredISI;
	}

	public void setHpConstant(double hpConstant) {
		this.hpConstant = hpConstant;
	}

	public double getHpConstant() {
		return hpConstant;
	}
	
	public boolean isHmP() {
		return hmP;
	}

	public void setHmP(boolean hmP) {
		this.hmP = hmP;
	}
}
